//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "KnowledgeRepository.h"
#include "StemNetStimulusBrain.h"
#include "Ieee80211MgmtBase.h"
#include "BasicBattery.h"

Define_Module(KnowledgeRepository);

class OLSR_HelloPiggybacking;

KnowledgeRepository::KnowledgeRepository() {
    // TODO Auto-generated constructor stub

}

KnowledgeRepository::~KnowledgeRepository() {
    cancelAndDelete(msgUpdate);
}


float KnowledgeRepository::getCongestionFactorCell() const {
    return congestion_factor_cell;
}

void KnowledgeRepository::setCongestionFactorCell(float congestionFactorCell) {
    congestion_factor_cell = congestionFactorCell;
}

float KnowledgeRepository::getCongestionFactorWifi() const {
    return congestion_factor_wifi;
}

void KnowledgeRepository::setCongestionFactorWifi(float congestionFactorWifi) {
    congestion_factor_wifi = congestionFactorWifi;
}

float KnowledgeRepository::getEnergyFactor() const {
    return energy_factor;
}

void KnowledgeRepository::setEnergyFactor(float energyFactor) {
    energy_factor = energyFactor;
}

STEM_ROLE_t KnowledgeRepository::getActualRule() const {
    return actual_rule;
}

void KnowledgeRepository::setStimulusIntervalTime(simtime_t stimulusTime) {
    stimulusIntervalTime = stimulusTime;
}

void KnowledgeRepository::setGatewayIntervalTime(simtime_t gatewayTime) {
    gatewayIntervalTime = gatewayTime;
}

void KnowledgeRepository::setStimulusOnRouting(bool stimOnRouting) {
    stimulusOnRouting = stimOnRouting;
}

void KnowledgeRepository::setActualRule(STEM_ROLE_t actualRule) {
    STEM_ROLE_t oldRule = actual_rule;
    actual_rule = actualRule;

    //changing the visualized icon
    switch(actual_rule){
    case STEM_STUB:
        this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/cellphone");

        if(oldRule == STEM_GATEWAY) {
            //generateGateway(false);
        }
        break;

    case STEM_TRANSIT:
        this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/accesspoint");

        if(oldRule == STEM_GATEWAY) {
            //generateGateway(false);
        }
        break;

    case STEM_GATEWAY:
        this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/antennatower");

        //generateGateway(true);
        break;
    }
}

void KnowledgeRepository::initialize(int stage) {

    if(stage == 0) {

        brainIn = findGate("brainIn");
        brainOut = findGate("brainOut");
        policyIn = findGate("policyIn");
        policyOut = findGate("policyOut");
        coopIn = findGate("cooperationIn");
        coopOut = findGate("cooperationOut");

        maxGatewayHops = par("maxGatewayHops");
        dbTimeout = par("dbTimeout");
        gamma_par = par("gamma");

        info_list.clear();
        stimulusMap.clear();
        gatewayMap.clear();

        energy_factor = 1;
        congestion_factor_wifi = 0;
        congestion_factor_cell = 0;

        msgUpdate = new cMessage("updateMsg");
        scheduleAt(simTime() + fabs(normal(1, 0.1)), msgUpdate);

        manet = check_and_cast<ManetRoutingCallable *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));

        WATCH_LIST(info_list);
        WATCH_MAP(stimulusMap);
        WATCH_MAP(gatewayMap);
        WATCH(actual_rule);
    }
}

void KnowledgeRepository::handleMessage(cMessage *msg) {
    if (msg->isSelfMessage()) {
        handleSelfMsg(msg);
    }
    else if (msg->getArrivalGateId() == brainIn) {
        delete(msg);
    }
    else if (msg->getArrivalGateId() == policyIn) {
        PersonalStatusUpdate *psu_msg = check_and_cast<PersonalStatusUpdate *>(msg);
        handlePolicyMsg(psu_msg);
        delete(msg);
    }
    else if (msg->getArrivalGateId() == coopIn) {
        delete(msg);
    }
    else {
        error("Unknown message received: %s", msg->getFullName());
        delete(msg);
    }
}

void KnowledgeRepository::handleSelfMsg(cMessage *msg) {
    if(msg == msgUpdate) {
        updateDB();

        scheduleAt(simTime() + fabs(normal(0.5, 0.01)), msgUpdate);
    }
    else {
        error("Unknown self message received: %s", msg->getFullName());
        delete(msg);
    }
}

void KnowledgeRepository::handlePolicyMsg(PersonalStatusUpdate *msg) {
    // update personal informations
    energy_factor = msg->getEnergy_f();
    congestion_factor_wifi = msg->getC_wifi_f();
    congestion_factor_cell = msg->getC_cell_f();
}

void KnowledgeRepository::graphicUpdate(void) {
    char buff_tt[256];
    char buff_t[256];
    int buffIdx_tt = 0;
    int buffIdx_t = 0;
    cModule * nodeMod = this->getParentModule()->getParentModule();
    //StemNetStimulusBrain *br = check_and_cast<StemNetStimulusBrain *>(this->getParentModule()->getSubmodule("brain"));

    // tt par ///////////////////////////////////////////////////////
    // stampo il mio ip
    buffIdx_tt += snprintf(buff_tt + buffIdx_tt, sizeof(buff_tt) - buffIdx_tt,
            "myIp: %s",
            manet->getManetAddress().str().c_str());

    // stampo l'elenco dei GW che conosco
    /*for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (it_list->info.role == STEM_GATEWAY) {
            buffIdx_tt += snprintf(buff_tt + buffIdx_tt, sizeof(buff_tt) - buffIdx_tt,
                    "\rGW: %s at %d hops",
                    it_list->info.address.str().c_str(), it_list->info.nhopDistance);
        }
    }

    // stampo in numero di GW totali che ci sono
    buffIdx_tt += snprintf(buff_tt + buffIdx_tt, sizeof(buff_tt) - buffIdx_tt,
            "\rTotal GW: %d",
            getTotalGWinNet());
     */

    // t par ///////////////////////////////////////////////////////

    //stampo i GW che conosco
    for (std::map<IPvXAddress, Gateway_t>::iterator it_map = gatewayMap.begin(); it_map != gatewayMap.end(); it_map++) {
        buffIdx_t += snprintf(buff_t + buffIdx_t, sizeof(buff_t) - buffIdx_t,
                                "\r%s:%d_%d ", (it_map->second.isON ? "GW" : "gw"), it_map->second.ipSrc.get4().getDByte(3), it_map->second.nHop);
        /*if(it_map->second.isON) {
            if(stimulusMap.count(it_map->first) == 0) {
                buffIdx_t += snprintf(buff_t + buffIdx_t, sizeof(buff_t) - buffIdx_t,
                        "\rGW: %s:%d  ", it_map->second.ipSrc.str().c_str(), it_map->second.nHop);
            }
            else {
                buffIdx_t += snprintf(buff_t + buffIdx_t, sizeof(buff_t) - buffIdx_t,
                        "\rGW: %s:%d %f:%f->%f  ", it_map->second.ipSrc.str().c_str(), it_map->second.nHop,
                        stimulusMap[it_map->first].stimulus, br->calculateActualThreshold(), br->calculateResponseFunction(stimulusMap[it_map->first].stimulus));
            }
        }*/
    }

    // stampo il mio contatore
    //buffIdx_t += snprintf(buff_t + buffIdx_t, sizeof(buff_t) - buffIdx_t,
    //                    "\rCounter: %d", br->forwardable_id);

    // stampo il mio ip
    buffIdx_t += snprintf(buff_t + buffIdx_t, sizeof(buff_t) - buffIdx_t,
            "\r%s", manet->getManetAddress().str().c_str());

    // stampo l'energia residua
    BasicBattery *bb = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getSubmodule("battery"));
    buffIdx_t += snprintf(buff_t + buffIdx_t, sizeof(buff_t) - buffIdx_t,
            "\r%.1f", bb->GetEnergy());

    if (ev.isGUI())
    {
        nodeMod->getDisplayString().setTagArg("tt", 0, buff_tt);
        nodeMod->getDisplayString().setTagArg("t", 0, buff_t);

        STEM_ROLE_t arule = getRealActualRule();
        switch(arule) {
        case STEM_GATEWAY:
            this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/antennatower");
            break;
        case STEM_TRANSIT:
            this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/accesspoint");
            break;
        case STEM_STUB:
            this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/cellphone");
            break;
        }

        /*if (actual_rule == STEM_TRANSIT) {
            // check WiFi queue
            Ieee80211MgmtBase *macMgmt = check_and_cast<Ieee80211MgmtBase *>(this->getParentModule()->getParentModule()->getSubmodule("wlan", 0)->getSubmodule("mgmt"));
            double c_wifi_factor = macMgmt->getDataQueueSize() / macMgmt->getDataQueueCapacity();

            if (c_wifi_factor == 0) {
                this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/cellphone");
            }
            else {
                this->getParentModule()->getParentModule()->getDisplayString().setTagArg("i", 0, "device/accesspoint");
            }
        }*/
    }
}

void KnowledgeRepository::updateDB(void) {
    std::list<NodeInfo_t>::iterator it_list;

    it_list = info_list.begin();
    while(it_list != info_list.end()) {
        if((simTime() - it_list->timestamp) > dbTimeout) {
            it_list = info_list.erase(it_list);
        }
        else {
            it_list++;
        }
    }

updateStimMap:
    for(std::map<IPvXAddress, Stimulus_t>::iterator it_st = stimulusMap.begin(); it_st != stimulusMap.end(); it_st++) {
        if ((simTime() - it_st->second.timestamp) > (stimulusIntervalTime * 3.0)) {
            stimulusMap.erase(it_st);

            // iterator corrupted after the erase operation, restarting...
            goto updateStimMap;
        }
    }

updateGWmap:
    for(std::map<IPvXAddress, Gateway_t>::iterator it_gw = gatewayMap.begin(); it_gw != gatewayMap.end(); it_gw++) {
        if ((simTime() - it_gw->second.timestamp) > (gatewayIntervalTime * 5.0)) {
            gatewayMap.erase(it_gw);

            // iterator corrupted after the erase operation, restarting...
            goto updateGWmap;
        }
    }

    //graphicUpdate();
}

void KnowledgeRepository::finish() {

}

float KnowledgeRepository::getAverageGatewayDistance_OLSR(OLSR *olsrModule) {
    OLSR_rtable *rt = olsrModule->getRoutingTable();

    float ris = 0;
    unsigned int countGW = 0;
    unsigned int sumDistance = 0;


    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (it_list->info.role == STEM_GATEWAY) {
            ManetAddress ma;
            OLSR_rt_entry *entry;

            ma.set(it_list->info.address.getIPvX());
            entry = rt->lookup(ma);

            if (entry != NULL) {
                sumDistance += entry->dist_;
                countGW++;
            }
        }
    }

    if (countGW > 0) {
        ris = ((float) sumDistance) / ((float) countGW);
    }

    return ris;
}

//float KnowledgeRepository::getAverageGatewayDistance_AODV(AODVUU *aodvModule) {
//    return 0;
//}

float KnowledgeRepository::getAverageGatewayDistance_generic(void) {
    float ris = -1;
    unsigned int countGW = 0;
    unsigned int sumDistance = 0;

    if (getActualRule() == STEM_GATEWAY) {
        return 0;
    }

    /*for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (it_list->info.role == STEM_GATEWAY) {
            sumDistance += it_list->info.nhopDistance;
            countGW++;
        }
    }*/

    for (std::map<IPvXAddress, Gateway_t>::iterator it_map = gatewayMap.begin(); it_map != gatewayMap.end(); it_map++) {
        sumDistance += it_map->second.nHop;
        countGW++;
    }

    if (countGW > 0) {
        ris = ((float) sumDistance) / ((float) countGW);
    }

    return ris;
}

int KnowledgeRepository::getSpecificGatewayDistance(IPvXAddress ipGW) {
    if((gatewayMap.count(ipGW) == 0) || (!gatewayMap[ipGW].isON)) {
        return 1000;    // return a big value because I don't know where is ipGW
    }
    else {
        return (gatewayMap[ipGW].nHop);
    }
}

float KnowledgeRepository::getAverageGatewayDistance(void) {

    OLSR *olsr = dynamic_cast<OLSR *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
    if(olsr) {
        return getAverageGatewayDistance_OLSR(olsr);
    }

//    AODVUU *aodv = dynamic_cast<AODVUU *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
//    if(aodv) {
//        return getAverageGatewayDistance_AODV(aodv);
//    }

    return getAverageGatewayDistance_generic();
}

void KnowledgeRepository::getNeighbourhoodInfoList_OLSR(std::list<node_state_info_t> &neigh_list, OLSR *olsrModule) {
    OLSR_rtable *rt = olsrModule->getRoutingTable();

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {

        if (it_list->info.role == STEM_GATEWAY) {
            ManetAddress ma;
            OLSR_rt_entry *entry;

            ma.set(it_list->info.address.getIPvX());
            entry = rt->lookup(ma);

            if ( (entry != NULL) && ((int) entry->dist_ <= maxGatewayHops) ) {
                // ok, we can add this gateway
                neigh_list.push_back(it_list->info);
            }
        }
    }
}

//void KnowledgeRepository::getNeighbourhoodInfoList_AODV(std::list<node_state_info_t> &neigh_list, AODVUU *aodvModule) {
//}

void KnowledgeRepository::getNeighbourhoodInfoList_generic(std::list<node_state_info_t> &neigh_list) {

    EV << manet->getManetAddress() << " Looking for neighbours..." << endl;

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {

        EV << "Node: " << it_list->info.address << endl;
        EV << "nHops: " << it_list->info.nhopDistance << endl;
        EV << "is OK?: " << (it_list->info.nhopDistance <= maxGatewayHops) << endl;

        //if ( (it_list->info.role == STEM_GATEWAY) && (it_list->info.nhopDistance <= maxGatewayHops) ) {
        if (it_list->info.nhopDistance < maxGatewayHops) {
            // ok, we can add this gateway
            neigh_list.push_back(it_list->info);
        }
    }
}

void KnowledgeRepository::getNeighbourhoodInfoList(std::list<node_state_info_t> &neigh_list) {

    OLSR *olsr = dynamic_cast<OLSR *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
    if(olsr) {
        getNeighbourhoodInfoList_OLSR(neigh_list, olsr);
        return;
    }

//    AODVUU *aodv = dynamic_cast<AODVUU *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
//    if(aodv) {
//        getNeighbourhoodInfoList_AODV(neigh_list, aodv);
//        return;
//    }

    getNeighbourhoodInfoList_generic(neigh_list);

}


void KnowledgeRepository::updateFromStimulus(IPvXAddress ipSrc, double stimulus, double energy, double congestion, int nHopCount) {
    if(stimulusMap.count(ipSrc) == 0) {
        // adding a new element in the map
        Stimulus_t newS;
        newS.ipSrc = ipSrc;
        newS.stimulus = stimulus;
        newS.energyFator = energy;
        newS.congestionFactor = congestion;
        newS.nHop = nHopCount;
        newS.timestamp = simTime();

        stimulusMap[ipSrc] = newS;
    }
    else {
        // update only
        Stimulus_t *stim_p = &stimulusMap[ipSrc];

        stim_p->stimulus = stimulus;
        stim_p->energyFator = energy;
        stim_p->congestionFactor = congestion;
        stim_p->timestamp = simTime();
        if (stim_p->nHop > nHopCount){
            stim_p->nHop = nHopCount;
        }
    }
}

void KnowledgeRepository::updateFromGatewayMsg(IPvXAddress ipSrc, bool gwON, double energy, double energyResidual, unsigned int nHopCount) {
    if(gatewayMap.count(ipSrc) == 0) {

        {//if(gwON) {
            // adding a new element in the map
            Gateway_t newS;
            newS.ipSrc = ipSrc;
            newS.isON = gwON;
            newS.nHop = nHopCount;
            newS.energyFator = energy;
            newS.energyResidual = energyResidual;
            newS.timestamp = simTime();

            gatewayMap[ipSrc] = newS;
        }
    }
    else {
        if(true){//if(gwON) {
            // update only
            Gateway_t *gw_p = &gatewayMap[ipSrc];

            gw_p->isON = gwON;
            gw_p->energyFator = energy;
            gw_p->energyResidual = energyResidual;
            if (gw_p->nHop > nHopCount){
                gw_p->nHop = nHopCount;
            }

            gw_p->timestamp = simTime();
            //gatewayMap[ipSrc].isON = gwON;
        }
        else {
            gatewayMap.erase(ipSrc);
        }
    }
}

void KnowledgeRepository::updateFromHello(node_state_info_t info) {
    std::list<NodeInfo_t>::iterator it_list;

    EV << endl << manet->getManetAddress() << " NEW STATUS MESSAGE ARRIVED" << endl;
    EV << "From: " << info.address << endl;
    EV << "Ccell: " << info.c_cell << endl;
    EV << "Cwifi: " << info.c_wifi << endl;
    EV << "Distance: " << info.distance << endl;
    EV << "Energy: " << info.energy << endl;
    EV << "nHop: " << info.nhopDistance << endl;
    EV << "Role: " << info.role << endl << endl;

    // check if is my address
    if (info.address == manet->getManetAddress()) {
        return;
    }

    // aggiungo 1-hop per quest'ultimo "salto" (i nodi settano questo parametro dal 'loro' punto di vista)
    info.nhopDistance++;

    for(it_list = info_list.begin(); it_list != info_list.end(); it_list++) {

        // check if the data is up to date
        //if( (it_list->info.address == info.address) && (info.msg_id > it_list->info.msg_id) ) {
        if (it_list->info.address == info.address) {
            // node already known, updating...

            if ((info.msg_id == 0) || (info.msg_id > it_list->info.msg_id)) {

                // saving last n_hops, save the best
                unsigned int nhop_old = it_list->info.nhopDistance;

                it_list->info = info;
                it_list->timestamp = simTime();

                if (nhop_old < it_list->info.nhopDistance) {
                    it_list->info.nhopDistance = nhop_old;
                }

                EV << "Updated value for: " << it_list->info.address << " - nHop: " << it_list->info.nhopDistance << endl;

            }

            break;
        }
    }

    if (it_list == info_list.end()) {
        // node not found, insert new value
        NodeInfo_t newInfo;
        newInfo.timestamp = simTime();
        newInfo.info = info;

        info_list.push_back(newInfo);

        EV << "Updated value for: " << newInfo.info.address << " - nHop: " << newInfo.info.nhopDistance << endl;
    }
}

IPvXAddress KnowledgeRepository::getNearestGateway_OLSR(OLSR *olsrModule) {
    OLSR_rtable *rt = olsrModule->getRoutingTable();
    int nearest = 100000;
    IPvXAddress ris = IPv4Address::LOOPBACK_ADDRESS;

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {

        if (it_list->info.role == STEM_GATEWAY) {
            ManetAddress ma;
            OLSR_rt_entry *entry;

            ma.set(it_list->info.address.getIPvX());
            entry = rt->lookup(ma);

            if (entry != NULL) {
                if((int)entry->dist_ < nearest) {
                    nearest = entry->dist_;
                    ris = it_list->info.address.getIPvX();
                }
            }
        }
    }

    return ris;
}

//IPvXAddress KnowledgeRepository::getNearestGateway_AODV(AODVUU *aodvModule) {
//    return IPv4Address::ALLONES_ADDRESS;
//}

IPvXAddress KnowledgeRepository::getNearestGateway_generic(void) {
    IPvXAddress ris = IPv4Address::LOOPBACK_ADDRESS;
    unsigned int nearest = 100000;

    /*for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {

        if (    (it_list->info.role == STEM_GATEWAY) &&
                (it_list->info.nhopDistance < nearest) ){
            nearest = it_list->info.nhopDistance;
            ris = it_list->info.address.getIPvX();
        }
    }*/

    if (ris == IPv4Address::LOOPBACK_ADDRESS) {
        // gateway not found in the STIMULUS database, search in the GATEWAY database
        for (std::map<IPvXAddress, Gateway_t>::iterator it_map = gatewayMap.begin(); it_map != gatewayMap.end(); it_map++) {
            if (it_map->second.isON) {
                if (it_map->second.nHop < nearest){
                    nearest = it_map->second.nHop;
                    ris = it_map->second.ipSrc;
                }
            }
        }
    }

    return ris;
}

int KnowledgeRepository::getHopNearestGateway(void) {
    unsigned int nearest = 100000;
    int ris = -1;

    for (std::map<IPvXAddress, Gateway_t>::iterator it_map = gatewayMap.begin(); it_map != gatewayMap.end(); it_map++) {
        if (it_map->second.isON) {
            if (it_map->second.nHop < nearest){
                nearest = it_map->second.nHop;
                ris = it_map->second.nHop;
            }
        }
    }

    return ris;
}

IPvXAddress KnowledgeRepository::getNearestGateway(void) {

    OLSR *olsr = dynamic_cast<OLSR *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
    if(olsr) {
        return getNearestGateway_OLSR(olsr);
    }

//    AODVUU *aodv = dynamic_cast<AODVUU *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
//    if(aodv) {
//        return getNearestGateway_AODV(aodv);
//    }

    return getNearestGateway_generic();
}

void KnowledgeRepository::getNeighIPlist_OLSR(std::list<IPvXAddress> &list, OLSR *olsrModule) {
    OLSR_rtable *rt = olsrModule->getRoutingTable();

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        ManetAddress ma;
        OLSR_rt_entry *entry;

        ma.set(it_list->info.address.getIPvX());
        entry = rt->lookup(ma);

        if ( (entry != NULL) && ((int) entry->dist_ == 1) ) {
            // ok, we can add this gateway
            list.push_back(it_list->info.address.getIPvX());
        }
    }
}

//void KnowledgeRepository::getNeighIPlist_AODV(std::list<IPvXAddress> &list, AODVUU *aodvModule) {
//}

void KnowledgeRepository::getNeighIPlist_generic(std::list<IPvXAddress> &list) {
    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (it_list->info.nhopDistance == 1) {
            // ok, we can add this gateway
            list.push_back(it_list->info.address.getIPvX());
        }
    }
}

void KnowledgeRepository::getNeighIPlist(std::list<IPvXAddress> &neigh_list) {

    OLSR *olsr = dynamic_cast<OLSR *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
    if(olsr) {
        getNeighIPlist_OLSR(neigh_list, olsr);
        return;
    }

//    AODVUU *aodv = dynamic_cast<AODVUU *>(this->getParentModule()->getParentModule()->getSubmodule("manetrouting"));
//    if(aodv) {
//        getNeighbourhoodInfoList_AODV(neigh_list, aodv);
//        return;
//    }

    getNeighIPlist_generic(neigh_list);

}

float KnowledgeRepository::getMaxEnergyFactor() {
    float max = getEnergyFactor();

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (max < it_list->info.energy) {
            max  = it_list->info.energy;
        }
    }

    return max;
}

float KnowledgeRepository::getMaxCongestionFactorWifi() {
    float max = 0;

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (max < it_list->info.c_wifi) {
            max  = it_list->info.c_wifi;
        }
    }

    return max;
}

float KnowledgeRepository::getMaxCongestionFactorCell() {
    float max = 0;

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (max < it_list->info.c_cell) {
            max = it_list->info.c_cell;
        }
    }

    return max;
}

float KnowledgeRepository::getMaxGatewayDistance() {
    float max = getAverageGatewayDistance();

    /*for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        if (max < it_list->info.distance) {
            max = it_list->info.distance;
        }
    }*/

    for (std::map<IPvXAddress, Gateway_t>::iterator it_map = gatewayMap.begin(); it_map != gatewayMap.end(); it_map++) {
        if (it_map->second.isON) {
            if (max < it_map->second.nHop) {
                max = it_map->second.nHop;
            }
        }
    }

    return max;
}

float KnowledgeRepository::getMaxNeighStimulus(void) {
    float max = 0;

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        double activeStimulus = (alpha_var * (1 - it_list->info.energy)) + (beta_var * it_list->info.c_cell);

        if (max < activeStimulus) {
            max = activeStimulus;
        }
    }

    return max;
}

int KnowledgeRepository::getTotalGWinNet(void) {
    int nGW = 0;

    for (std::map<IPvXAddress, Gateway_t>::iterator it_map = gatewayMap.begin(); it_map != gatewayMap.end(); it_map++) {
        if (it_map->second.isON) {
            nGW++;
        }
    }
    if (getActualRule() == STEM_GATEWAY) {
        nGW++;
    }
    return nGW;
    /*
    int ris = gatewayMap.size();

    if (getActualRule() == STEM_GATEWAY) {
        ris++;
    }

    return ris;
    */
}

float KnowledgeRepository::getAverageNeighStimulus(void) {
    double sumStim = 0;
    int countStim = 0;

    for (std::list<NodeInfo_t>::iterator it_list = info_list.begin(); it_list != info_list.end(); it_list++) {
        double activeStimulus = (alpha_var * (1 - it_list->info.energy)) + (beta_var * it_list->info.c_cell);

        sumStim += activeStimulus;
        countStim++;
    }

    if(countStim > 0) {
        return (sumStim / ((double) countStim));
    }
    else {
        return 0;
    }
}

double KnowledgeRepository::calculateRouteThreshold(void) {
    //double e_max, d_max, c_max;
    double e_max, c_max;
    double alpha_fact, beta_fact;

    // calculating the max values
    e_max = getMaxEnergyFactor();
    //d_max = getMaxGatewayDistance();
    c_max = getMaxCongestionFactorWifi();

    alpha_fact = beta_fact = 0;

    if (e_max > 0) {
        alpha_fact = alpha_var * (getEnergyFactor() / (e_max));
    }
    if (c_max > 0) {
        double beta_wifi;
        //double beta_distance;

        beta_wifi = 1.0 - (getCongestionFactorWifi() / c_max);
        //beta_distance = 1 - (getAverageGatewayDistance() / d_max);

        //beta_fact = beta_var * 0.5 * (beta_wifi + beta_distance); //TODO
        beta_fact = beta_var * beta_wifi;
    }

    return (alpha_fact + beta_fact);
}

bool KnowledgeRepository::checkRREQforward(double route_stimulus) {
    if (stimulusOnRouting) {
        bool toForward = false;
        double risp, rand, st_exp, th_exp;
        double routeThreshold = calculateRouteThreshold();

        st_exp = pow(route_stimulus, responseExponent);
        th_exp = pow(routeThreshold, responseExponent);

        risp = st_exp / (st_exp + (1 - th_exp));
        risp = pow(risp, totalResponseExponent);

        rand = dblrand();

        if (rand < risp) {
            toForward = true;
        }

        return toForward;
    }
    else {
        return true;
    }
}

double KnowledgeRepository::getRoutingStimulus(int attemptsCount) {
    double ris = 0;

    // set the congestion factor of the WiFi queue
    ris = getCongestionFactorWifi();

    // add the retry factor
    ris += gamma_par * attemptsCount;

    if (ris > 1) {
        ris = 1;
    }

    return ris;
}

STEM_ROLE_t KnowledgeRepository::getRealActualRule() {
    if (actual_rule == STEM_GATEWAY) {
        return STEM_GATEWAY;
    }
    else {
        Ieee80211MgmtBase *macMgmt = check_and_cast<Ieee80211MgmtBase *>(this->getParentModule()->getParentModule()->getSubmodule("wlan", 0)->getSubmodule("mgmt"));
        cQueue *macQ = macMgmt->getDataQueue();
        bool rout = false;

        for (cQueue::Iterator iter(*macQ); !iter.end(); iter++) {
            Ieee80211DataFrameWithSNAP *pktmac = dynamic_cast<Ieee80211DataFrameWithSNAP *>(iter());

            if (pktmac) {

                if(strncmp(pktmac->getName(), "UDP_STEM_Data", 13) == 0) {

                    char ip[16];
                    int nmsg;
                    IPvXAddress ipaddr;

                    memset(ip, 0 ,sizeof(ip));
                    sscanf(pktmac->getName(), "UDP_STEM_Data-%d_%s", &nmsg, ip);

                    ipaddr = IPv4Address(ip);

                    if(ipaddr != manet->getManetAddress().getIPv4()) {
                        rout = true;
                    }
                }
            }

            if (rout == true) {
                break;
            }
        }

        if(rout) {
            return STEM_TRANSIT;
        }
        else{
            return STEM_STUB;
        }
    }
}

